﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="EventsHelper.cs" company="Mark Christian D. Menchavez">
//   Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace MCDM
{
    using System;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Reflection;
    using System.Runtime.CompilerServices;

    /// <summary>
    /// Helper functions for raising events.
    /// </summary>
    public static class EventsHelper
    {
        private delegate void Async(Delegate @delegate, object[] args);

        /// <summary>
        /// Publishes an event to the specified handler.
        /// </summary>
        /// <param name="handler">The handler.</param>
        /// <param name="sender">The sender.</param>
        /// <param name="eventArgs">The <see cref="EventArgs" /> instance containing the event data.</param>
        public static void Publish(EventHandler handler, object sender, EventArgs eventArgs)
        {
            PublishEvent(handler, sender, eventArgs);
        }

        /// <summary>
        /// Publishes an event to the specified handler.
        /// </summary>
        /// <typeparam name="TEventArgs">The type of the event argument.</typeparam>
        /// <param name="handler">The handler.</param>
        /// <param name="sender">The sender.</param>
        /// <param name="eventArgs">The event argument.</param>
        public static void Publish<TEventArgs>(EventHandler<TEventArgs> handler, object sender, TEventArgs eventArgs) 
            where TEventArgs : EventArgs
        {
            PublishEvent(handler, sender, eventArgs);
        }

        /// <summary>
        /// Publishes an event to the specified handler.
        /// </summary>
        /// <typeparam name="TSender">The type of the sender.</typeparam>
        /// <typeparam name="TEventArgs">The type of the event argument.</typeparam>
        /// <param name="handler">The handler.</param>
        /// <param name="sender">The sender.</param>
        /// <param name="eventArgs">The event argument.</param>
        public static void Publish<TSender, TEventArgs>(EventHandler<TSender, TEventArgs> handler, TSender sender, TEventArgs eventArgs) 
            where TEventArgs : EventArgs
        {
            PublishEvent(handler, sender, eventArgs);
        }

        /// <summary>
        /// Publishes an event to the specified handler asynchronously.
        /// </summary>
        /// <param name="handler">The handler.</param>
        /// <param name="sender">The sender.</param>
        /// <param name="eventArgs">The <see cref="EventArgs" /> instance containing the event data.</param>
        public static void PublishAsync(EventHandler handler, object sender, EventArgs eventArgs)
        {
            PublishEventAsync(handler, sender, eventArgs);
        }

        /// <summary>
        /// Publishes an event to the specified handler asynchronously.
        /// </summary>
        /// <typeparam name="TEventArgs">The type of the event argument.</typeparam>
        /// <param name="handler">The handler.</param>
        /// <param name="sender">The sender.</param>
        /// <param name="eventArgs">The event argument.</param>
        public static void PublishAsync<TEventArgs>(EventHandler<TEventArgs> handler, object sender, TEventArgs eventArgs) 
            where TEventArgs : EventArgs
        {
            PublishEventAsync(handler, sender, eventArgs);
        }

        /// <summary>
        /// Publishes an event to the specified handler asynchronously.
        /// </summary>
        /// <typeparam name="TSender">The type of the sender.</typeparam>
        /// <typeparam name="TEventArgs">The type of the event argument.</typeparam>
        /// <param name="handler">The handler.</param>
        /// <param name="sender">The sender.</param>
        /// <param name="eventArgs">The event argument.</param>
        public static void PublishAsync<TSender, TEventArgs>(EventHandler<TSender, TEventArgs> handler, TSender sender, TEventArgs eventArgs) 
            where TEventArgs : EventArgs
        {
            PublishEventAsync(handler, sender, eventArgs);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private static void PublishEvent(Delegate @delegate, params object[] arguments)
        {
            if (@delegate == null)
            {
                return;
            }

            var invocationList = @delegate.GetInvocationList();
            foreach (var invocation in invocationList)
            {
                try
                {
                    invocation.DynamicInvoke(arguments);
                }
                catch (TargetInvocationException e)
                {
                    Debug.WriteLineIf(e.InnerException != null, e.InnerException);
                }
            }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private static void PublishEventAsync(Delegate @delegate, params object[] arguments)
        {
            if (@delegate == null)
            {
                return;
            }

            var invocationList = @delegate.GetInvocationList();

            Async async = InvokeDelegate;
            AsyncCallback callback = OnCallback;
            foreach (var invocation in invocationList)
            {
                async.BeginInvoke(invocation, arguments, callback, async);
            }
        }

        private static void InvokeDelegate(Delegate @delegate, object[] arguments)
        {
            var target = @delegate.Target as ISynchronizeInvoke;
            if (target != null)
            {
                if (target.InvokeRequired)
                {
                    target.Invoke(@delegate, arguments);
                    return;
                }
            }

            try
            {
                @delegate.DynamicInvoke(arguments);
            }
            catch (TargetInvocationException e)
            {
                Debug.WriteLineIf(e.InnerException != null, e.InnerException);
            }
        }

        private static void OnCallback(IAsyncResult asyncResult)
        {
            var @delegate = asyncResult.AsyncState as Async;
            if (@delegate != null)
            {
                @delegate.EndInvoke(asyncResult);
            }

            asyncResult.AsyncWaitHandle.Close();
        }
    }
}
